%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% HighLife (Conway's Game of Life)
% by Trevor Thompson & Kevin Mehlbrech
% CSE 322
% 12/5/2008
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% main goal
startGame :- getNumGens(X), getGrid(Y), statistics(runtime,_), 
highlife(X,Y), statistics(runtime,[Head,Tail]), writeTime(Tail), 
statistics(trailused,M), writeSpace(M).

% ----Engine----
highlife(NumGens,InputStr) :- isSquare(InputStr,Width), string_to_list(InputStr,L), initGrid(L,Width,GridList), 
	applyRules(1,NumGens,GridList,Width).

% checks for squareness, and returns the width in W
isSquare(Z,W) :- (string_length(Z,X), sqrt(X,Y), F is float_fractional_part(Y), 
	F == 0.0, W is truncate(Y)) -> true ; (write('Error: the supplied grid is not square.'), nl, fail).

applyRules(CurrentGen,CurrentGen,Grid,Width) :- printGrid(Grid,CurrentGen,Width), !.
applyRules(CurrentGen,TotGens,Grid,Width) :- CurrentGen < TotGens, printGrid(Grid,CurrentGen,Width), nextGen(Grid,Width,OutGrid), 
	NextGen is CurrentGen + 1, applyRules(NextGen,TotGens,OutGrid,Width).

nextGen(InGrid,Width,OutGrid) :- flatten(InGrid,FlatList), length(FlatList,L), doWork(L,0,InGrid,InGrid,Width,OutGrid). 

doWork(Max,Idx,_,CGrid,_,OutGrid) :- Max == Idx, OutGrid = CGrid, !.
doWork(Max,Idx,InGrid,CGrid,Width,OutGrid) :-  R is floor(Idx / Width) + 1, C is (Idx mod Width) + 1, 
	handleCell(R,C,InGrid,CGrid,X), NIdx is Idx + 1, doWork(Max,NIdx,InGrid,X,Width,OutGrid).  

handleCell(R,C,Grid,UGrid,OutGrid) :- getLiveNeighbors(R,C,Grid,N), updateCell(N,R,C,UGrid,OutGrid).

updateCell(2,_R,_C,Grid,OutGrid) :- OutGrid = Grid, !.
updateCell(3,R,C,Grid,OutGrid) :- updateCell(6,R,C,Grid,OutGrid), !.
updateCell(6,R,C,Grid,OutGrid) :- nth1(R,Grid,Row), replace(C,Row,111,NewRow), replace(R,Grid,NewRow,OutGrid), !. 
%select(NewRow,OutGrid,Rest), select(Row,Grid,Rest), !.
updateCell(_,R,C,Grid,OutGrid) :- nth1(R,Grid,Row), replace(C,Row,45,NewRow), replace(R,Grid,NewRow,OutGrid).
%select(NewRow,OutGrid,Rest), select(Row,Grid,Rest).

getLiveNeighbors(R,C,Grid,Result) :- findall(R+C, check(R,C,Grid), Bag), length(Bag, Result).

check(R,C,Grid) :- R1 is R - 1, C1 is C - 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell). 
check(R,C,Grid) :- R1 is R - 1, C1 is C, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R - 1, C1 is C + 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R, C1 is C - 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R, C1 is C + 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R + 1, C1 is C - 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R + 1, C1 is C, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).
check(R,C,Grid) :- R1 is R + 1, C1 is C + 1, nth1(R1,Grid,Row), nth1(C1,Row,Cell), isAlive(Cell).

printGrid(Grid,Num,Width) :- string_concat('Generation ', Num, GNum), write(GNum), nl, writeRows(Grid,Width,1), nl.

writeRows(_Grid,Width,CurrentRow) :- CurrentRow > Width, !.
writeRows(Grid,Width,CurrentRow) :- nth1(CurrentRow, Grid, Row),forall(member(Cell,Row),writef('%n',[Cell])), nl, NextRow is CurrentRow + 1, 
writeRows(Grid,Width,NextRow).

% ----Utility functions----

% get number of generations from command line
getNumGens(X) :- current_prolog_flag(argv, Y), nth1(9, Y, Z), atom_number(Z,X).

% get grid input string from command line
getGrid(X) :- current_prolog_flag(argv, Y), nth1(10, Y, X).

% split input string (grid) into lists of [Symbol] where Symbol is '-' or 'o'
initGrid([],_,_). % :- !.
initGrid(G,W,OutList) :- append(X,Xs,G), length(X,L), L == W, select(X,OutList,Rest), initGrid(Xs,W,Rest).

isAlive(S) :- S == 111 -> true ; fail.

replace(Idx, Row, Item, New) :- append(Left,[_|Rest],Row), length(Left,L), L is Idx - 1, 
	append([Item],Rest,NewRight), append(Left,NewRight,New).

writeTime(Time) :- string_concat('Finished in ', Time, Tmp), 
string_concat(Tmp, ' milliseconds.', Out), write(Out), nl.

writeSpace(Space) :- string_concat('Utilized ', Space, Tmp), 
string_concat(Tmp, ' total bytes of unshared memory.', Out), write(Out), 
nl.  
